/*
	aiai.cc		AI vs AI
	Copyright (c) 2005 Kriang Lerdsuwanakij
	email:		lerdsuwa@users.sourceforge.net

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "config.h"

#include <pthread.h>

#include <pwd.h>
#include <sys/types.h>
#include <time.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>

#include <exception>
#include <stdexcept>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <string>
#include <deque>
#include <vector>
#include <sstream>

#include "alphabeta.h"
#include "board.h"
#include "boardio.h"
#include "game.h"
#include "randboard.h"
#include "rand.h"
#include "gtp.h"

#include "gtstream.h"

// Grab version number in VERSION variable
#undef VERSION
char *
#include "scripts/version"
;

#ifdef _
#undef _
#endif

#ifdef N_
#undef N_
#endif

#include <libintl.h>
#define _(x) gettext(x)
#define N_(x) x

const char *prog_name = "aiai";
const char *prog_ver = VERSION;

int	*fifo_black_to_client;
int	*fifo_black_to_server;
int	*fifo_white_to_client;
int	*fifo_white_to_server;

int	pid_black;
int	pid_white;

const char *cmd_black = 
	"/home/lerdsuwa/devel/rhino/gtp-rhino --level=3";
//	"/opt-ignore/grhino-0.14.0/bin/gtp-rhino --level=3";
const char *cmd_white = 
	"/home/lerdsuwa/devel/rhino/gtp-rhino --level=3";
//	"/opt-ignore/grhino-0.14.0/bin/gtp-rhino --level=2";

int	game_win_black = 0;
int	game_win_white = 0;
int	game_draw = 0;
int	total_score_black = 0;
int	total_score_white = 0;
int	last_score_black = 0;
int	last_score_white = 0;
bool	synchro_rand = false;

const char *gtpcmd_boardsize = "boardsize 8\n";
const char *gtpcmd_clear_board = "clear_board\n";
const char *gtpcmd_setup_board = "grhino-setup_board ";
const char *gtpcmd_genmove_black = "genmove black\n";
const char *gtpcmd_genmove_white = "genmove white\n";
const char *gtpcmd_play_black = "play black ";
const char *gtpcmd_play_white = "play white ";
const char *gtpcmd_quit = "quit\n";

int	fifo1[2], fifo2[2], fifo3[2], fifo4[2];

void	init()
{
	srand(time(NULL));

					// Default parameters
	log_move = true;
	redo_ai_move = true;
	start_game_mode = START_GAME_INITIAL;
	synchro_rand = false;
}

void	read_response(int h)
{
	char	c;
	bool	found_newline = false;

	for ( ; ; ) {
		if (read(h, &c, 1) != 1)
			throw std::runtime_error(_("cannot read from client"));
		if (c == '\n') {
			if (found_newline)
				return;
			else
				found_newline = true;
		}
		else
			found_newline = false;
	}
}

int	read_move(int h)
{
	char	c;
	bool	found_newline = false;
	bool	found_col = false;
	bool	found_row = false;
	int	col = 0;
	int	row = 0;

	for ( ; ; ) {
		if (read(h, &c, 1) != 1)
			throw std::runtime_error(_("cannot read from client"));
		if (c == '\n') {
			if (found_newline) {
				if (found_col && found_row)
					return xy_to_pos(col, row);
				else
					throw std::runtime_error(_("cannot read from client"));
			}
			else
				found_newline = true;
		}
		else {
			found_newline = false;
			if (c >= '1' && c <= '8') {
				found_row = true;
				row = c - '1';
			}
			else if (c >= 'A' && c <= 'H') {
				found_col = true;
				col = c - 'A';
			}
			else if (c >= 'a' && c <= 'h') {
				found_col = true;
				col = c - 'a';
			}
				// FIXME: Handle pass
		}
	}
}

void	connect_clients()
{
	fifo_black_to_client = fifo1;
	fifo_black_to_server = fifo2;
	fifo_white_to_client = fifo3;
	fifo_white_to_server = fifo4;

	if (pipe(fifo_black_to_client))
		throw std::runtime_error(_("cannot create pipe"));
	if (pipe(fifo_black_to_server))
		throw std::runtime_error(_("cannot create pipe"));
	if (pipe(fifo_white_to_client))
		throw std::runtime_error(_("cannot create pipe"));
	if (pipe(fifo_white_to_server))
		throw std::runtime_error(_("cannot create pipe"));

	pid_black = fork();
	if (pid_black == -1)
		throw std::runtime_error(_("cannot run black GTP client"));
	else if (pid_black == 0) {
		if (dup2(fifo_black_to_client[0], STDIN_FILENO) == -1)
			throw std::runtime_error(_("cannot redirect client"));
		if (dup2(fifo_black_to_server[1], STDOUT_FILENO) == -1)
			throw std::runtime_error(_("cannot redirect client"));
		close(fifo_black_to_client[0]);
		close(fifo_black_to_client[1]);
		close(fifo_black_to_server[0]);
		close(fifo_black_to_server[1]);
		close(fifo_white_to_client[0]);
		close(fifo_white_to_client[1]);
		close(fifo_white_to_server[0]);
		close(fifo_white_to_server[1]);
		if (execlp("/bin/sh", "sh", "-c", cmd_black, 0) == -1)
			throw std::runtime_error(_("cannot run black GTP client"));
	}

	pid_white = fork();
	if (pid_white == -1)
		throw std::runtime_error(_("cannot run white GTP client"));
	else if (pid_white == 0) {
		if (dup2(fifo_white_to_client[0], STDIN_FILENO) == -1)
			throw std::runtime_error(_("cannot redirect client"));
		if (dup2(fifo_white_to_server[1], STDOUT_FILENO) == -1)
			throw std::runtime_error(_("cannot redirect client"));
		close(fifo_black_to_client[0]);
		close(fifo_black_to_client[1]);
		close(fifo_black_to_server[0]);
		close(fifo_black_to_server[1]);
		close(fifo_white_to_client[0]);
		close(fifo_white_to_client[1]);
		close(fifo_white_to_server[0]);
		close(fifo_white_to_server[1]);
		if (execlp("/bin/sh", "sh", "-c", cmd_white, 0) == -1)
			throw std::runtime_error(_("cannot run white GTP client"));
	}

	close(fifo_black_to_client[0]);
	close(fifo_black_to_server[1]);
	close(fifo_white_to_client[0]);
	close(fifo_white_to_server[1]);

	write(fifo_black_to_client[1],
	      gtpcmd_boardsize, strlen(gtpcmd_boardsize));
	write(fifo_white_to_client[1],
	      gtpcmd_boardsize, strlen(gtpcmd_boardsize));
	read_response(fifo_black_to_server[0]);
	read_response(fifo_white_to_server[0]);
}

void	disconnect_clients()
{
	write(fifo_black_to_client[1], gtpcmd_quit, strlen(gtpcmd_quit));
	write(fifo_white_to_client[1], gtpcmd_quit, strlen(gtpcmd_quit));
	read_response(fifo_black_to_server[0]);
	read_response(fifo_white_to_server[0]);

	close(fifo_black_to_client[1]);
	close(fifo_black_to_server[0]);
	close(fifo_white_to_client[1]);
	close(fifo_white_to_server[0]);

	waitpid(pid_black, 0, 0);
	waitpid(pid_white, 0, 0);
}

void	game_loop(bool reverse = false)
{
	if (reverse) {
		int *temp = fifo_black_to_client;
		fifo_black_to_client = fifo_white_to_client;
		fifo_white_to_client = temp;

		temp = fifo_black_to_server;
		fifo_black_to_server = fifo_white_to_server;
		fifo_white_to_server = temp;
	}

	time_player = 0;

	if (start_game_mode == START_GAME_INITIAL) {
		write(fifo_black_to_client[1],
		      gtpcmd_clear_board, strlen(gtpcmd_clear_board));
		write(fifo_white_to_client[1],
		      gtpcmd_clear_board, strlen(gtpcmd_clear_board));
		read_response(fifo_black_to_server[0]);
		read_response(fifo_white_to_server[0]);
	}
	else {
		std::string	buf = gtpcmd_setup_board;
		for (int i = 0; i < 64; ++i) {
			switch (cur_game_info.board_ptr->board[i]) {
				case EMPTY:
					buf += '_';
					break;
				case BLACK:
					buf += '@';
					break;
				case WHITE:
					buf += 'O';
					break;
			}
		}
		buf += '\n';
		write(fifo_black_to_client[1], buf.c_str(), buf.size());
		write(fifo_white_to_client[1], buf.c_str(), buf.size());
		read_response(fifo_black_to_server[0]);
		read_response(fifo_white_to_server[0]);
	}

	int color = BLACK;
	for ( ; ; ) {
		if (!cur_game_info.board_ptr->can_play(color)) {
			color = switch_color(color);
			if (!cur_game_info.board_ptr->can_play(color))
				break;
		}

		if (color == BLACK) {
			write(fifo_black_to_client[1],
			      gtpcmd_genmove_black, strlen(gtpcmd_genmove_black));
			int pos = read_move(fifo_black_to_server[0]);

			std::string	buf = gtpcmd_play_black;
			buf += pos_to_x(pos) + 'A';
			buf += pos_to_y(pos) + '1';
			buf += '\n';
			write(fifo_white_to_client[1], buf.c_str(), buf.size());
			read_response(fifo_white_to_server[0]);

			cur_game_info.place_piece(pos, time_player);
			time_player = 0;
		}
		else {
			write(fifo_white_to_client[1],
			      gtpcmd_genmove_white, strlen(gtpcmd_genmove_white));
			int pos = read_move(fifo_white_to_server[0]);

			std::string	buf = gtpcmd_play_white;
			buf += pos_to_x(pos) + 'A';
			buf += pos_to_y(pos) + '1';
			buf += '\n';
			write(fifo_black_to_client[1], buf.c_str(), buf.size());
			read_response(fifo_black_to_server[0]);

			cur_game_info.place_piece(pos, time_player);
			time_player = 0;
		}

		color = switch_color(color);
	}

	int	black_score = cur_game_info.board_history[cur_game_info.num_history-1].board_black_score();
	int	white_score = cur_game_info.board_history[cur_game_info.num_history-1].board_white_score();
	switch (cur_game_info.get_game_result()) {
		case game_info::game_result_end:
			adjust_score(black_score, white_score);
			break;
		case game_info::game_result_timeout_black:
			black_score = 0;
			white_score = 64;
			break;
		case game_info::game_result_timeout_white:
			black_score = 64;
			white_score = 0;
			break;
		case game_info::game_result_resign_black:
			black_score = 0;
			white_score = 64;
			break;
		case game_info::game_result_resign_white:
			black_score = 64;
			white_score = 0;
			break;
		default:
			black_score = 64;
			white_score = 0;
			break;
	}

	if (reverse) {
		last_score_black = white_score;
		last_score_white = black_score;
	}
	else {
		last_score_black = black_score;
		last_score_white = white_score;
	}

	total_score_black += last_score_black;
	total_score_white += last_score_white;

	if (last_score_black > last_score_white)
		game_win_black++;
	else if (last_score_black < last_score_white)
		game_win_white++;
	else
		game_draw++;

	log_history("gtp-rhino.log", "black", "white");

	if (reverse) {
		int *temp = fifo_black_to_client;
		fifo_black_to_client = fifo_white_to_client;
		fifo_white_to_client = temp;

		temp = fifo_black_to_server;
		fifo_black_to_server = fifo_white_to_server;
		fifo_white_to_server = temp;
	}
}

int	main_real(int argc, char *argv[])
{
	unsigned num_game = 1;
	bool num_game_set = false;
	if (argc > 1) {
		for (int i = 1; i < argc; ++i) {
			std::string arg = argv[i];
			unsigned u;

			if (arg == "-h" || arg == "--help") {
				std::cout << prog_name << ' ' << prog_ver << _(" - AI vs AI for GTP-Rhino\n");
				std::cout << _("(c) 2005 Kriang Lerdsuwanakij\n\n");
				gtout(std::cout, _("Usage:   %$ [options] [num_games]\n")) << prog_name;
				std::cout << _("Available options:\n");
				std::cout << _("     -r N, --rand=N    Use random board with N initial pieces.\n");
				std::cout << _("                       N=4..32, N even\n");
				std::cout << _("     -s N, --sync=N    Use synchro random mode with N initial pieces,\n");
				std::cout << _("                       i.e. for each board an additional game is played.\n");
				std::cout << _("                       with players switching their assigned color.\n");
				std::cout << _("                       Actual number of games played = 2 x num_games.\n");
				std::cout << _("                       N=4..32, N even\n");
				return 0;
			}
			else if (arg == "-v" || arg == "--version") {
				std::cout << prog_name << ' ' << prog_ver << '\n';
				return 0;
			}
			else if (process_unsigned_option(argc, argv, arg, i,
							 "-r", "--rand",
							 4, 32, u)) {
				if (u % 2) {
					gtstream bufstr;
					gtout(bufstr, _("invalid argument for option `%$\'")) <<  arg;
					throw std::runtime_error(bufstr.str());
				}
				start_game_mode = START_GAME_RANDOM;
				start_random_game_pieces = u;
				synchro_rand = false;
			}
			else if (process_unsigned_option(argc, argv, arg, i,
							 "-s", "--sync",
							 4, 32, u)) {
				if (u % 2) {
					gtstream bufstr;
					gtout(bufstr, _("invalid argument for option `%$\'")) <<  arg;
					throw std::runtime_error(bufstr.str());
				}
				start_game_mode = START_GAME_RANDOM;
				start_random_game_pieces = u;
				synchro_rand = true;
			}
			else {
				u = 0;
				size_t i;
				for (i = 0; i < arg.size(); ++i) {
					if (arg[i] < '0' || arg[i] > '9')
						break;
					u = u*10 + arg[i]-'0';
				}
				if (i == arg.size()) {
					if (!num_game_set) {
						num_game = u;
						num_game_set = true;
					}
					else {
						gtstream bufstr;
						gtout(bufstr, _("extra argument `%$\'")) <<  arg;
						throw std::runtime_error(bufstr.str());
					}
				}
				else {
					gtstream bufstr;
					gtout(bufstr, _("unknown option `%$\'")) <<  arg;
					throw std::runtime_error(bufstr.str());
				}
			}
		}
	}

	connect_clients();

	std::string	file = get_user_home_dir();
	file += '/';
	file += "gtp-rhino.info";
	std::ofstream of(file.c_str(), std::ios::app);
	if (of) {
		of << "---------------------------------------------------\n";

		if (start_game_mode == START_GAME_INITIAL)
			of << "Board: Initial\n";
		else if (synchro_rand)
			of << "Board: Synchro Random " << start_random_game_pieces << '\n';
		else
			of << "Board: Random " << start_random_game_pieces << '\n';

		of << "Black: " << cmd_black << '\n';
		of << "White: " << cmd_black << '\n';

		of << "Start time: ";
		char buffer[80];
		time_t t = time(0);
		if (strftime(buffer, 80, "%Y.%m.%d_%H:%M:%S.%Z", localtime(&t)))
			of << buffer;
		else
			of << t;
		of << '\n';

	}

	for (unsigned i = 0; i < num_game; ++i) {
		if (num_game > 1)
			std::cout << "Game " << i+1 << ' ' << std::flush;

		eval_new_game();
		new_game(cur_game_info);
		game_loop();
		std::cout << last_score_black << '-' << last_score_white;

		if (synchro_rand) {
			byte_board_info b(cur_game_info.board_history[0]);
			eval_new_game();
			new_game(cur_game_info, b);

			game_loop(true);
			std::cout << ' ' << last_score_black << '-' << last_score_white;
		}

		std::cout << '\n';
	}

	if (of) {
		of << "End time: ";
		char buffer[80];
		time_t t = time(0);
		if (strftime(buffer, 80, "%Y.%m.%d_%H:%M:%S.%Z", localtime(&t)))
			of << buffer;
		else
			of << t;
		of << '\n';

		of << "Game: " << game_win_black << '-' << game_win_white << '-'
		   << game_draw << '\n';
		of << "Score: " << total_score_black << '-' << total_score_white << '\n';
	}

	if (num_game > 1) {
		std::cout << "Game: " << game_win_black << '-' << game_win_white << '-'
			  << game_draw << '\n';
		std::cout << "Score: " << total_score_black << '-' << total_score_white << '\n';
	}

	disconnect_clients();
	return 0;
}

int	main(int argc, char *argv[])
{
	try {
		init();
		return main_real(argc, argv);
	}
	catch (std::exception &e) {
		std::cout << std::flush;
		gtout(std::cerr, _("%$: %$\n")) << prog_name << e.what();
	}
	catch (const char *s) {
		std::cout << std::flush;
		gtout(std::cerr, _("%$: exception %$\n")) << prog_name << s;
	}
	catch (...) {
		std::cout << std::flush;
		gtout(std::cerr, _("%$: unknown exception\n")) << prog_name;
	}
	return 1;
}
